#include "apue.h"
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

extern int makethread(void *(*fn)(void *), void *);

struct to_info {
    void (*to_fn)(void *);
    void *to_arg;
    struct timespec to_wait;
};

#define SECTONSEC 1000000000    /* seconds to nanoseconds */

#if !defined(CLOCK_REALTIME) || defined(BSD)
#define clock_nanosleep(ID, FL, REO, REM) nanosleep((REO), (REM))
#endif

#ifndef CLOCK_REALTIME
#define CLOCK_REALTIME 0
#define USECTONSEC 1000     /*microseconds to nanoseconds */

void clock_gettime(int id, struct timespec *tsp) {
    struct timeval tv; 
    gettimeofday(&tv, NULL);
    tsp.tv_sec= tv.tv_sec;
    tsp.tv_nsec= tv.tv_usec * USECTONSEC;
}
#endif

void * timeout_helper(void *arg) {
    struct to_info *to;
    to= (struct to_info *)arg;
    clock_nanosleep(CLOCK_REALTIME, 0, &to->to_wait, NULL);
    (*to->to_fn)(to->to_arg);
    free(arg);
    return(0);
}

void timeout(const struct timespec *when, void (*func)(void *), void *arg) {
    struct timespec now;
    struct to_info *to;
    int err;
    clock_gettime(CLOCK_REALTIME, &now);
    if (now.tv_sec > when->tv_sec || (now.tv_sec == when->tv_sec && now.tv_nsec > when->tv_nsec)) {
        to= malloc(sizeof(struct to_info));
        if (to != NULL) {
            to->to_fn= func;
            to->to_arg= arg;
            to->to_wait.tv_sec= when->tv_sec - now.tv_sec;
            if (when->tv_nsec >= now.tv_nsec) {
                to->to_wait.tv_nsec= when->tv_nsec - now.tv_nsec;
            } else {
                to->to_wait.tv_sec--; 
                to->to_wait.tv_nsec= when->tv_nsec + SECTONSEC - now.tv_nsec;
            }
            err= makethread(&timeout_helper, to);
            if (err == 0)
                return;
            else
                free(to);
        }

    }
    /**
     * We get here if (a) when <= now, or (b) malloc fails, or
     * (c) we can't make a thread, so we just call the function now
     *
     */
    (*func)(arg);
}

pthread_mutexattr_t attr;
pthread_mutex_t mutex;

void retry(void *arg) {
    pthread_mutex_lock(&mutex);
    /* perform retry steps...*/
    pthread_mutex_unlock(&mutex);
}

int main(void) {
    int err, condition, arg;
    struct timespec when;

    if ((err= pthread_mutexattr_init(&attr)) != 0)
        err_exit(err, "pthread_mutexatt_init failed");
    if ((err= pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0)
        err_exit(err, "cant set recursive type");
    if ((err= pthread_mutex_init(&mutex, &attr)) != 0)
        err_exit(err, "cant create recursive mutex");

    pthread_mutex_lock(&mutex);
    /**
     * Check the condition under the protection of a lock to make 
     * the check and the call to timeout atomic
     */
    if (condition) {
        clock_gettime(CLOCK_REALTIME, &when);
        when.tv_sec+=10; 
        timeout(&when, retry, (void *)(unsigned long)arg);
    }
    pthread_mutex_unlock(&mutex);

    /* continue processing... */
    exit(0);
}



// void *timeout(struct timespec *tsp, )
